import base64
import os
import re
import time
from time import sleep

import allure
import cv2
from appium.webdriver.common.appiumby import AppiumBy
from appium.webdriver.common.touch_action import TouchAction
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.support.wait import WebDriverWait

from config import BASE_PATH
from tool.get_log import GetLogger

log = GetLogger.get_log()


class Base:

    def __init__(self, driver):
        log.info(f'正在初始化驱动对象{driver}')
        self.driver = driver

    # 6.2 checked
    def base_find_element(self, loc, timeout=20, poll=0.5):
        # page :pages/fishPond/fishPond.html:
        self.__auto_swith(loc)
        log.info(f'正在查找元素{loc}')
        style, value = loc[0], loc[1]
        if self.driver.context != 'NATIVE_APP':
            if len(loc) >= 3:
                page = loc[2]
                for i in range(5):
                    try:
                        if page in self.driver.title:
                            break
                        log.info("进行循环")
                    except:
                        pass
                    log.info("需要切换句柄")
                    self.__switch_to_webview_handle()
                    if i == 3:
                        sleep(timeout)
                        # pass
                    if i == 4:
                        log.error(f"切换window次数用尽，未找到元素{loc}")
                        log.error(NoSuchElementException('window次数用尽，未找到对应页面元素'))
                        raise NoSuchElementException('window次数用尽，未找到对应页面元素')
            if "INVISIBLE" in self.driver.title:
                raise Exception("句柄异常，流程进入到非预期的句柄中，请查看上一个元素操作")
        if style == AppiumBy.IMAGE:
            self.driver.switch_to.context('NATIVE_APP')
            # 元素需要传入base64字符串，只能在原生context使用
            encoded = base64.b64encode(open(value, 'rb').read())
            value = encoded.decode()
        return WebDriverWait(self.driver, timeout=timeout, poll_frequency=poll) \
            .until(lambda x: x.find_element(style, value))
        # return WebDriverWait(self.driver, timeout=timeout, poll_frequency=poll) \
        #     .until(lambda x: x.find_element(*loc))

    def __auto_swith(self, loc):
        if len(loc) == 2:
            self.driver.switch_to.context('NATIVE_APP')
        else:
            self.driver.switch_to.context('WEBVIEW_com.tencent.mm:appbrand0')

    # 6.2 checked
    def __switch_to_webview_handle(self):
        # sleep(2)
        # if "INVISIBLE" in self.driver.title:
        # log.info(f"{self.driver.window_handles}")
        # aa = self.driver.window_handles
        for window in self.driver.window_handles:
            self.driver.switch_to.window(window)
            # a = self.driver.title
            if ":VISIBLE" in self.driver.title:
                log.info(f"切换到句柄{window},页面{self.driver.title}")
                break

    def base_find_elements(self, loc, timeout=20, poll=0.5):
        self.base_find_element(loc)
        style, value = loc[0], loc[1]
        return WebDriverWait(self.driver, timeout=timeout, poll_frequency=poll) \
            .until(lambda x: x.find_elements(style, value))

    # 6.2 checked
    def base_click_element(self, loc,timeout=20):
        log.info(f"正在点击元素{loc}")
        self.base_find_element(loc,timeout=timeout).click()

    # 6.2 checked
    def base_input_value(self, loc, value):
        # 支持定位外层元素
        el = self.base_find_element(loc)
        log.info(f'正在清空元素{loc}')
        sleep(2)
        el.click()  # 激活输入框为可用状态
        # self.base_click_center_coordinate(loc)
        sleep(2)
        # el.clear() 方法不好用
        # 使用keycode方案替代
        try:
            el.clear()
        except:
            self.__key_clear()
        log.info(f'向元素{loc}输入{value}')
        el.send_keys(value)
        self.driver.press_keycode(4)  # 不使用unicodeKeyboard，小程序有些输入框无法输入，返回键keycode用于关闭键盘

    # 6.2 checked
    def __key_clear(self):
        self.driver.press_keycode(29, 28672)  # 全选
        self.driver.press_keycode(112)  # 删除

    # 6.2 checked
    def base_clear_value(self, loc):
        log.info(f'正在清空元素{loc}')
        el = self.base_find_element(loc)
        el.click()
        try:
            el.clear()
        except:
            self.__key_clear()

    def base_input_value_js(self, loc, msg):
        el2 = self.base_find_element(loc)
        # el2 = self.base_find_element(loc2)
        # # js = 'arguments[0].innerText=arguments[1];'
        # js = f'arguments[0].setAttribute("value", "{msg}");'
        js2 = f'arguments[0].setAttribute("contenteditable", "true");'
        # self.driver.execute_script(js, el, msg)
        self.driver.execute_script(js2, el2, msg)

    def base_input_value_adb(self, loc, msg):
        sleep(1)
        self.base_click_element(loc)
        self.__key_clear()
        sleep(1)
        for i in msg:
            sleep(0.2)
            os.system(f"adb shell input text {i}")
        sleep(2)
        self.driver.press_keycode(4)

    # 6.2 checked
    def base_get_current_page_url(self):
        log.info(f"正在获取当前页的URL")
        return re.findall(r":(.*?):", self.driver.title)[0]

    # 6.2 checked
    def base_get_current_package_name(self):
        name = self.driver.current_package
        log.info(f'获取元素当前应用的包名为{name}')
        return name

    # 6.2 checked
    def base_get_current_activity(self):
        activity = self.driver.current_activity
        log.info(f'获取元素当前应用的启动名为{activity}')
        return activity

    # 6.2 checked
    def base_get_element_text(self, loc):
        log.info(f'正在获取元素{loc}的text值')
        return self.base_find_element(loc).text

    # 6.2 checked
    def base_get_element_attribute_value(self, loc, att):
        log.info(f'获取元素{loc}的属性{att}的值')
        return self.base_find_element(loc).get_attribute(att)

    def base_if_app_is_install(self, package):
        log.info(f"判断app{package}是否已经安装")
        path = BASE_PATH + os.sep + 'data' + os.sep + 'com.izaodao.french.apk'
        if self.driver.is_app_installed(package):
            log.info(f"app{package}已经安装,正在卸载")
            self.driver.remove_app(package)
            log.info(f"重新安装app{package}")
            self.driver.install_app(path)
        else:
            log.info(f"app{package}没有安装，正在进行安装")
            self.driver.install_app(path)

    # 6.2 checked
    def base_get_screenshot_as_file(self, module_name, reason, image_el=None, msg_en=None):
        if image_el:
            location = image_el.rect
        current_context = self.driver.context
        self.driver.switch_to.context('NATIVE_APP')
        path = BASE_PATH + os.sep + 'img' + os.sep + module_name + '-{}.png' \
            .format(time.strftime("%Y_%"
                                  "m_%d %H_%M_%S"))
        log.info(f'正在进行截图，路径为{path}')
        list1 = path.split('\\')
        list1.reverse()
        filename = list1[0]
        self.driver.get_screenshot_as_file(path)
        if image_el:
            path = self.__base_mark_img(location, path, msg_en)
        self.__base_write_img(path, filename, reason)
        self.driver.switch_to.context(current_context)

    # 私有方法，将截图添加到报告中
    @staticmethod
    def __base_write_img(path, filename, reason):
        log.info(f'正在将截图{filename}写入报告中')
        with open(path, 'rb') as f:
            log.info('打开文件')
            allure.attach(f.read(), f'{reason}，截图名字:{filename}', allure.attachment_type.PNG)

    # 6.8 checked
    # opencv识别后的图像，标记识别区域,仅适用于opencv元素，其他正常元素不适用，并注意图片读写路径不能有中文
    def __base_mark_img(self, location, path, msg_en):
        img = cv2.imread(path)
        p1 = int(location['x']), int(location['y'])
        p2 = int(location['x'] + location['width']), int(location['y'] + location['height'])
        cv2.rectangle(img, p1, p2, (0, 255, 0), 2)
        if msg_en:
            font = cv2.FONT_HERSHEY_SIMPLEX
            imgzi = cv2.putText(img, f"{msg_en}", (int(location['x']), int(location['y'] - 15)), font, 1,
                                (0, 255, 255), 4)
        new_path = "_mark.".join(path.split("."))
        cv2.imwrite(new_path, img)
        return new_path

    # 6.2 checked(无惯性)
    def base_up_to_down_windowscroll_js(self, loc, offset=0):
        """
        适用于全局滚动条，从顶部滑动
        :param loc: 目标元素
        :param offset: 垂直方向偏移量（支持负值）
        :return:
        """
        self.driver.execute_script("window.scrollTo(0,0);")
        y_window = self.driver.get_window_size()['height']
        el = self.base_find_element(loc)
        y_el = el.location['y']
        el_height = el.size.get("height")
        self.driver.execute_script(f"window.scrollTo(0,{y_el - y_window + el_height + offset});")

    # 6.2 checked(无惯性)
    def base_scroll_element_into_view_js(self, loc, istop='true', offset_loc=None):
        """
        适用于局部+全屏滚动条
        :param loc:目标元素定位
        :param istop:元素置于滚动区（视窗内）顶部（true）,底部（false）
        :param offset_loc: 偏移元素定位（此元素应为目标元素附近元素，
        设置时，此元素会在顶部显示，可能会有缺失，这样目标元素就会处于合十位置），仅适用于全局滚动，否则不生效
        :return:
        """
        el = self.base_find_element(loc)
        self.driver.execute_script(f"arguments[0].scrollIntoView({istop});", el)
        if offset_loc:
            self.driver.execute_script("window.scrollTo(0,0);")
            el_offset = self.base_find_element(offset_loc)
            y_offset_el = el_offset.location['y']
            self.driver.execute_script(f"window.scrollTo(0,{y_offset_el});")

    def base_swipe_to_bottom(self,times=10):
        log.info("正在调用从上向下滑到底方法")
        size = self.driver.get_window_size()
        while True:
            source = self.driver.page_source
            for i in range(times):
                self.driver.swipe(size['width'] * 0.5, size['height'] * 0.7, size['width'] * 0.5, size['height'] * 0.3)
            if source == self.driver.page_source:
                log.info("已滑到底")
                break


    def base_left_to_right_find_element(self, loc_area, loc):
        log.info("正在调用从左向右滑动方法")

        # 查找区域元素
        el = self.base_find_element(loc_area, timeout=3)
        # 获取区域元素的位置,是区域的起始点（左上角），（y坐标）,返回值是一个字典
        y = el.location.get("y")
        x = el.location.get("x")
        # print(el.location)
        # 获取区域元素宽高
        width = el.size.get("width")
        height = el.size.get("height")
        # print(el.size)
        # 计算起始位置和终止位置的坐标点
        start_x = x + width * 0.8
        start_y = y + height * 0.5
        end_x = x + width * 0.2
        end_y = y + height * 0.5
        # print(start_x,start_y,end_x,end_y)
        while True:
            source = self.driver.page_source
            # print(source)
            try:
                self.base_find_element(loc, timeout=3)
                log.info(f"找到目标元素{loc}")
                return True
            except:
                log.info(f"未找到元素{loc},进行滑动，重新寻找")
                self.driver.swipe(start_x, start_y, end_x, end_y, duration=500)

            if source == self.driver.page_source:
                log.info("滑到最后一个屏幕，未找到元素")
                return False
                # raise NoSuchElementException

    def base_up_to_down_find_element(self, loc_area, loc):
        log.info("正在调用从上向下滑动方法")
        # 查找区域元素
        el = self.base_find_element(loc_area, timeout=3)
        # 获取区域元素的位置,是区域的起始点（左上角），（y坐标）,返回值是一个字典
        y = el.location.get("y")
        x = el.location.get("x")
        print(el.location)
        # 获取区域元素宽高
        width = el.size.get("width")
        height = el.size.get("height")
        # 计算起始位置和终止位置的坐标点
        start_x = x + width * 0.5
        start_y = y + height * 0.2
        end_x = x + width * 0.5
        end_y = y + height * 0.8
        while True:
            source = self.driver.page_source
            # print(source)
            try:
                self.base_find_element(loc, timeout=3)
                log.info(f"找到目标元素{loc}")
                return True
            except:
                log.info(f"未找到元素{loc},进行滑动，重新寻找")
                self.driver.swipe(start_x, start_y, end_x, end_y, duration=500)

            if source == self.driver.page_source:
                log.info("滑到最后一个屏幕，未找到元素")
                return False

    # 6.2 checked
    def base_if_element_exist(self, loc, timeout=20):
        try:
            el = self.base_find_element(loc, timeout)
            log.info(f'已经找到元素{loc}')
            if loc[0] == AppiumBy.IMAGE:
                return True, el
            else:
                return True
        except:
            log.info(f'没有找到元素{loc}')
            return False

    # 点击坐标，支持原生和webview,无需切换context
    def base_native_click_location(self, x, y):
        log.info(f'正在点击坐标点{x, y}')
        TouchAction(self.driver).tap(x=x, y=y).perform()

    def base_click_center_coordinate(self, loc):
        el = self.base_find_element(loc)
        y = el.location.get("y")
        x = el.location.get("x")
        width = el.size.get("width")
        height = el.size.get("height")
        self.base_native_click_location(x + 0.5 * width, y + 0.5 * height)

    # opencv初始化，设置缩放比例，设置对比阈值
    def base_opencv_init(self, template_path, threshold=None):
        current_context = self.driver.context
        self.driver.switch_to.context('NATIVE_APP')
        log.info(f'正在初始化opencv配置')
        img = cv2.imread(template_path)
        width, height = img.shape[1], img.shape[0]
        self.driver.update_settings({"fixImageTemplateScale": True})
        self.driver.update_settings({"defaultImageTemplateScale": self.driver.get_window_size()['width'] / width})
        log.info(f"模板文件为：{template_path}，分辨率为：{width},{height}")
        log.info(f"实际分辨率为：{self.driver.get_window_size()}")
        log.info(f"设置缩放比例为：{self.driver.get_window_size()['width'] / width}")
        if threshold:
            log.info(f"设置识别阈值:{threshold}")
            self.driver.update_settings({"imageMatchThreshold": threshold})
        log.info(f"初始化完成")
        self.driver.switch_to.context(current_context)

    # 根据图像识别，图像定位坐标起始点，进行偏移量点击
    def base_opencv_offset_click(self, loc, offset_x, offset_y):
        el = self.base_find_element(loc)
        location = el.rect  # {'x': 53, 'y': 124, 'width': 567, 'height': 127}
        tap_x = location["x"] + offset_x
        tap_y = location["y"] + offset_y
        self.base_native_click_location(tap_x, tap_y)
